/*
   Copyright (c) 2020, zoomdy
   MicroAmpereII is licensed under Mulan PSL v2.
   You can use this software according to the terms and conditions of the Mulan PSL v2.
   You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
   EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
   MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
   See the Mulan PSL v2 for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "pt/pt.h"

#include "drv_usb_hw.h"
#include "cdc_acm_core.h"
#include "hal_cdc.h"
#include "hal_assert.h"


extern uint8_t packet_sent, packet_receive;
extern uint32_t receive_length;


usb_core_driver USB_OTG_dev;


typedef struct
{
    hal_cdc_req_t* head;
    hal_cdc_req_t* tail;
}hal_cdc_fifo_t;


typedef struct
{
    struct pt pt;
    hal_cdc_fifo_t req;
}hal_cdc_recv_t;

typedef struct
{
    struct pt pt;
    hal_cdc_fifo_t req;
}hal_cdc_send_t;

static hal_cdc_recv_t hal_cdc_recv_i;
static hal_cdc_send_t hal_cdc_send_i;


PT_THREAD(hal_cdc_recv_task(void));
PT_THREAD(hal_cdc_send_task(void));

static void hal_cdc_fifo_init(hal_cdc_fifo_t* fifo);
static void hal_cdc_fifo_remove_head(hal_cdc_fifo_t* fifo);
static void hal_cdc_fifo_append_tail(hal_cdc_fifo_t* fifo, hal_cdc_req_t* req);


void hal_cdc_init_(void)
{
    PT_INIT(&hal_cdc_recv_i.pt);
    PT_INIT(&hal_cdc_send_i.pt);

    hal_cdc_fifo_init(&hal_cdc_recv_i.req);
    hal_cdc_fifo_init(&hal_cdc_send_i.req);

    eclic_global_interrupt_enable();
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL2_PRIO2);

    USB_OTG_dev.dev.desc.dev_desc = (uint8_t*)&device_descriptor;
    USB_OTG_dev.dev.desc.config_desc = (uint8_t *)&configuration_descriptor;
    USB_OTG_dev.dev.desc.strings = usbd_strings;

    usb_rcu_config();
    usb_timer_init();
    usb_intr_config();
    usbd_init (&USB_OTG_dev, USB_CORE_ENUM_FS, &usbd_cdc_cb);
}


void usb_cdc_poll_(void)
{
    hal_cdc_recv_task();
    hal_cdc_send_task();
}


PT_THREAD(hal_cdc_recv_task(void))
{
    hal_cdc_recv_t* self = &hal_cdc_recv_i;

    PT_BEGIN(&self->pt);

    PT_WAIT_UNTIL(&self->pt, USBD_CONFIGURED == USB_OTG_dev.dev.cur_status);

    while (1) {
        PT_WAIT_UNTIL(&self->pt, self->req.head != NULL);

        packet_receive = 0;
        usbd_ep_recev(&USB_OTG_dev, CDC_ACM_DATA_OUT_EP, self->req.head->buffer, self->req.head->length);
        PT_WAIT_UNTIL(&self->pt, packet_receive == 1);

        self->req.head->length = receive_length;
        self->req.head->err = HAL_ERR_NONE;

        hal_cdc_fifo_remove_head(&self->req);
    }

    PT_END(&self->pt);
}


PT_THREAD(hal_cdc_send_task(void))
{
    hal_cdc_send_t* self = &hal_cdc_send_i;

    PT_BEGIN(&self->pt);

    PT_WAIT_UNTIL(&self->pt, USBD_CONFIGURED == USB_OTG_dev.dev.cur_status);

    while (1) {
        PT_WAIT_UNTIL(&self->pt, self->req.head != NULL);

        packet_sent = 0;
        usbd_ep_send(&USB_OTG_dev, CDC_ACM_DATA_IN_EP, self->req.head->buffer, self->req.head->length);
        PT_WAIT_UNTIL(&self->pt, packet_sent == 1);

        self->req.head->err = HAL_ERR_NONE;

        hal_cdc_fifo_remove_head(&self->req);
    }

    PT_END(&self->pt);
}


hal_err_t hal_cdc_recv(hal_cdc_req_t* req)
{
    hal_cdc_recv_t* self = &hal_cdc_recv_i;

    hal_cdc_fifo_append_tail(&self->req, req);

    return HAL_ERR_NONE;
}


hal_err_t hal_cdc_send(hal_cdc_req_t* req)
{
    hal_cdc_send_t* self = &hal_cdc_send_i;

    hal_cdc_fifo_append_tail(&self->req, req);

    return HAL_ERR_NONE;
}


static void hal_cdc_fifo_init(hal_cdc_fifo_t* fifo)
{
    fifo->head = NULL;
    fifo->tail = NULL;
}


static void hal_cdc_fifo_remove_head(hal_cdc_fifo_t* fifo)
{
    if(fifo->head == fifo->tail) {
        fifo->head = fifo->tail = NULL;
    } else {
        fifo->head = fifo->head->next;
    }
}


static void hal_cdc_fifo_append_tail(hal_cdc_fifo_t* fifo, hal_cdc_req_t* req)
{
    HAL_ASSERT(req != NULL);
    HAL_ASSERT(req->buffer != NULL);
    HAL_ASSERT(req->length > 0 && req->length <= HAL_CDC_PACKET_SIZE);

    req->err = HAL_ERR_INPROGRESS;
    req->next = NULL;
    if(fifo->head == NULL) {
        HAL_ASSERT(fifo->tail == NULL);
        fifo->head = fifo->tail = req;
    } else {
        HAL_ASSERT(fifo->tail != NULL);
        fifo->tail->next = req;
        fifo->tail = req;
    }
}
